package top.jnaw.jee.utils;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.ActiveRecordPlugin;
import com.jfinal.plugin.activerecord.Db;
import com.jfinal.plugin.activerecord.Model;
import com.jfinal.plugin.activerecord.Page;
import com.jfinal.plugin.activerecord.Record;
import com.jfinal.template.source.ClassPathSourceFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import top.jnaw.jee.platform.Consts;
import top.jnaw.jee.platform.Consts.ModelResultType;
import top.jnaw.jee.platform.Consts.Result;

/**
 * Created by lc on 17-6-1. <p> Model 工具类
 */
public class Models {

  public static void addSqlTemplate(ActiveRecordPlugin arp) {
    arp.getEngine().setSourceFactory(new ClassPathSourceFactory());

    arp.addSqlTemplate("sql/others.sql");
  }

  /**
   * 压缩 Model 或 Record 对象 <p>
   * <pre>
   * 'a.b': 1 => a:{b:1}
   * 'a.b.c': 1 => a:{b:{c:1}}
   * </pre>
   */
  public static JSONObject compress(Object object) {
    if (null == object) {
      return null;
    }

    JSONObject root = new JSONObject();

    String[] attrs = new String[0];
    if (object instanceof Model) {
      attrs = ((Model) object)._getAttrNames();
    } else if (object instanceof Record) {
      attrs = ((Record) object).getColumnNames();
    }

    for (String attr : attrs) {
      String[] splits = attr.split("\\.");

      final int length = splits.length;
      if (length > 1) {
        JSONObject parent = root;

        for (int i = 0; i < length; ++i) {
          JSONObject child;

          if (parent.containsKey(splits[i])) {
            child = (JSONObject) parent.get(splits[i]);
          } else {
            child = new JSONObject();
          }

          parent.put(splits[i], child);
          parent = child;

          // [Neo] break before last loop
          if (i + 1 == length - 1) {
            break;
          }
        }

        if (object instanceof Model) {
          parent.put(splits[length - 1], ((Model) object).get(attr));
        } else {
          parent.put(splits[length - 1], ((Record) object).get(attr));
        }

      } else if (1 == length) {
        if (object instanceof Model) {
          root.put(attr, ((Model) object).get(attr));
        } else {
          root.put(attr, ((Record) object).get(attr));
        }
      }
    }

    return root;
  }

  /**
   * 压缩 List 存储的 Model 或 Record 数据到树形结构
   * <pre>
   *   基本规则：压缩层次，合并相同 id 的对象，合并相同数值的基础数据类型
   *
   *   - a.b => a{b}
   *   - a.id.foo=1, a.id.foo=2 => a{id, foo[{1},{2}]}
   *
   *   特殊处理
   *
   *   $: 不合并，只保留结果集第一次出现的数值
   *   #: 默认压缩规则，强制节点结果为数组
   *   !: 递归合并没有 id 对象的情况，不合并基础数据类型
   * </pre>
   */
  public static JSONObject compressList(List list) {
    JSONObject result = new JSONObject();

    if (null == list || 0 == list.size()) {
      return result;
    }

    int length = list.size();
    JSONObject line = key2array(compress(list.get(0)));
    for (int i = 1; i < length; ++i) {
      _merge(line, compress(list.get(i)));
    }

    result = clean(line);
    return result;
  }

  private static JSONObject _merge(JSONObject line, JSONObject newLine) {
    for (String key : line.keySet()) {
      JSONArray firstArray = line.getJSONArray(key);
      Object firstValue = firstArray.get(firstArray.size() - 1);
      boolean isJson = firstValue instanceof JSONObject;

      if (key.endsWith("$")) {
        // [Neo] .final signal
        continue;
      }

      if (isJson) {
        JSONObject json = (JSONObject) firstValue;
        JSONObject newJson = newLine.getJSONObject(key);

        String idKey = getIdKey(json);

        if (StrKit.notBlank(idKey)) {

          if (null == json.getJSONArray(idKey) || null == json
              .getJSONArray(idKey).get(0) || null == newJson.get(idKey)) {
            continue;
          }
          Integer id = Integer
              .parseInt(json.getJSONArray(idKey).get(0).toString());
          Integer newId = Integer.parseInt(newJson.get(idKey).toString());

//          if (null == id || null == newId) {
//            continue;
//          }

          // [Neo] .same id
          if (id.equals(newId)) {
            for (String k : json.keySet()) {
              if (json.getJSONArray(k).get(0) instanceof JSONObject) {
                _merge(json, newJson);
              }
            }
          } else {
            // [Neo] .id mismatch, add into parent
            firstArray.add(key2array(newJson));
          }

        } else {
          // [Neo] .without id, add array or merge again
          if (key.endsWith("!")) {
            _merge(json, newJson);
          } else {
            firstArray.add(key2array(newJson));
          }
        }

      } else {
        // [Neo] .plain key-value, merge as default
        if (key.endsWith("!")) {
          firstArray.add(newLine.get(key));
        } else {
          if (!firstArray.contains(newLine.get(key))) {
            firstArray.add(newLine.get(key));
          }
        }
      }
    }

    return line;
  }

  public static JSONArray cast(List list) {
    JSONArray result = new JSONArray();

    if (null != list && list.size() > 0) {
      for (Object o : list) {
        if (o instanceof Model) {
          result.add(JSONObject.parse(((Model) o).toJson()));
        } else if (o instanceof Record) {
          result.add(JSONObject.parse(((Record) o).toJson()));
        } else {
          result.add(o);
        }
      }
    }

    return result;
  }

  public static JSONArray compressLists(List list) {
    JSONArray result = new JSONArray();
    if (null != list && list.size() > 0) {
      for (Object o : list) {
        result.add(compress(o));
      }
    }
    return result;
  }

  public static JSONObject compressPage(Page page) {
    return compressPage(page, false, "");
  }

  public static JSONObject compressPage(Page page, Boolean compress,
      String key) {
    JSONObject result = new JSONObject();

    if (null == page || null == page.getList()) {
      return result;
    }

    result.put(Consts.JSON_RES_PAGE, page.getPageNumber());
    result.put(Consts.JSON_RES_SIZE, page.getPageSize());
    result.put(Consts.JSON_RES_TOTAL, page.getTotalPage());
    result.put(Consts.JSON_RES_ROWS, page.getTotalRow());
    result.put(Consts.JSON_RES_RESULT, Result.kSuccess.ordinal());

    if (0 == page.getList().size()) {
      return result;
    }

    JSONArray array = new JSONArray();
    if (compress) {
      array = compressList(page.getList()).getJSONArray(key);
//      result.put(Consts.JSON_RES_ROWS, array.size());
//      result.put(Consts.JSON_RES_TOTAL, array.size() / page.getPageSize());
    } else {
      for (Object o : page.getList()) {
        array.add(compress(o));
      }
    }
    result.put(Consts.JSON_RES_DATA, array);
    return result;
  }

  public static JSONObject compressPage(List list, List list_rows) {
    JSONObject result = new JSONObject();

    if (null == list) {
      return result;
    }
    result.put(Consts.JSON_RES_RESULT, Result.kSuccess.ordinal());

    JSONArray array = new JSONArray();
    for (Object o : list) {
      array.add(compress(o));
    }
    JSONArray array_rows = new JSONArray();
    for (Object o : list_rows) {
      array_rows.add(compress(o));
    }
    if (array.size() > 0) {
      if (null == array.getJSONObject(0).getInteger("id")) {
        result.put(Consts.JSON_RES_DATA, new JSONArray());
      } else {
        result.put(Consts.JSON_RES_DATA, array);
      }
    }
    if (array_rows.size() > 0) {
      if (null == array_rows.getJSONObject(0)
          .getInteger("id")) {
        result.put(Consts.JSON_RES_ROWS, 0);
      } else {
        result.put(Consts.JSON_RES_ROWS, array_rows.size());
      }
    }
    return result;
  }

  private static JSONObject key2array(JSONObject obj) {
    JSONObject result = (JSONObject) obj.clone();

    for (String key : result.keySet()) {
      JSONArray array;

      if (result.get(key) instanceof JSONArray) {
        array = (JSONArray) result.get(key);
      } else if (result.get(key) instanceof JSONObject) {
        array = new JSONArray();
        array.add(key2array(result.getJSONObject(key)));
      } else {
        array = new JSONArray();
        array.add(result.get(key));
      }

      result.put(key, array);
    }

    return result;
  }

  public static JSONArray merge(JSONArray lastArray, JSONObject obj) {
    JSONObject last = lastArray.getJSONObject(lastArray.size() - 1);

    for (String key : last.keySet()) {
      JSONArray lastKeyArray = last.getJSONArray(key);
      Object lastValue = lastKeyArray.get(lastKeyArray.size() - 1);
      boolean isLastJson = lastValue instanceof JSONObject;

      if (isLastJson) {
        JSONObject sub = (JSONObject) lastValue;
        JSONObject objSub = obj.getJSONObject(key);

        if (sub.containsKey("id")) {
          Integer lastInt = sub.getJSONArray("id").getInteger(0);
          Integer curInt = objSub.getInteger("id");

          if (null == lastInt || null == curInt) {
            continue;
          }

          boolean isSameId = (lastInt - curInt == 0);

          // [Neo] .auto merge same id
          if (isSameId && !key.endsWith("!")) {
            for (String k : sub.keySet()) {
              if (sub.getJSONArray(k).get(0) instanceof JSONObject) {
                // [Neo] .force array or not
                if (k.endsWith("!")) {
                  sub.getJSONArray(k).add(key2array(objSub.getJSONObject(k)));
                  // [Neo] .not the final
                } else if (!k.endsWith("$")) {
                  merge(sub.getJSONArray(k), objSub.getJSONObject(k));
                }
              }
            }

          } else {
            lastKeyArray.add(key2array(objSub));
          }

        } else {
          // [Neo] .not the final object
          if (!key.endsWith("$")) {
            merge(lastKeyArray, obj.getJSONObject(key));
          }
        }

      } else {
        // [Neo] force array or not
        if (key.endsWith("!")) {
          lastKeyArray.add(obj.get(key));
        } else {
          if (!lastKeyArray.contains(obj.get(key))) {
            lastKeyArray.add(obj.get(key));
          }
        }
      }
    }

    return lastArray;
  }

  public static JSONObject clean(JSONObject obj) {
    Map<String, Object> cleanMap = new HashMap<>();

    String idKey = getIdKey(obj);
    if (StrKit.notBlank(idKey) &&
        null == obj.getJSONArray(idKey).getInteger(0)) {
      obj.clear();
      return obj;
    }

    for (String key : obj.keySet()) {
      if (obj.get(key) instanceof JSONArray) {
        JSONArray array = obj.getJSONArray(key);
        int size = array.size();
        for (int i = 0; i < size; ++i) {
          if (array.get(i) instanceof JSONArray
              && array.getJSONArray(i).get(0) instanceof JSONObject) {
            int s = array.getJSONArray(i).size();
            for (int j = 0; j < s; ++j) {
              clean(array.getJSONArray(i).getJSONObject(j));
            }
          } else if (array.get(i) instanceof JSONObject) {
            JSONObject j = clean(array.getJSONObject(i));

            if (j.isEmpty()) {
              array = Consts.EMPTY_ARRAY;
              obj.put(key, array);
            }
          }

          if (key.endsWith("#") || key.endsWith("$") || key.endsWith("!")
              || 1 == size) {
            cleanMap.put(key, array);
          }
        }
      }
    }

    for (String key : cleanMap.keySet()) {
      JSONArray array = (JSONArray) cleanMap.get(key);
      if (key.endsWith("$")) {
        obj.remove(key);
        obj.put(key.substring(0, key.length() - 1),
            array.size() > 0 ? array.get(0) : Consts.EMPTY_ARRAY);
      } else if (key.endsWith("#") || key.endsWith("!")) {
        obj.remove(key);
        obj.put(key.substring(0, key.length() - 1), array);
      } else {
        obj.put(key, array.size() > 0 ? array.get(0) : Consts.EMPTY_ARRAY);
      }
    }

    return obj;
  }

  /**
   * 生成整数结果的 JSON
   */
  public static JSONObject toJson(Result result) {
    JSONObject json = new JSONObject();
    json.put(Consts.JSON_RES_RESULT, result.ordinal());
    json.put(Consts.JSON_RES_DATA, result);
    return json;
  }

  public static JSONObject toJsonId(Model model, Result result) {
    JSONObject json = new JSONObject();

    if (Result.kSuccess == result && null != model) {
      json.put(Consts.JSON_RES_RESULT, Result.kSuccess.ordinal());
      JSONObject object = new JSONObject();
      object.put("id", model.getInt("id"));
      json.put(Consts.JSON_RES_DATA, object);
    } else {
      json.put(Consts.JSON_RES_RESULT, result.ordinal());
      json.put(Consts.JSON_RES_DATA, Consts.EMPTY_OBJECT);
    }

    return json;
  }

  public static JSONObject toJsonId(Record record, Result result) {
    JSONObject json = new JSONObject();

    if (Result.kSuccess == result && null != record) {
      json.put(Consts.JSON_RES_RESULT, Result.kSuccess.ordinal());
      JSONObject object = new JSONObject();
      object.put("id", record.getInt("id"));
      json.put(Consts.JSON_RES_DATA, object);
    } else {
      json.put(Consts.JSON_RES_RESULT, result.ordinal());
      json.put(Consts.JSON_RES_DATA, Consts.EMPTY_OBJECT);
    }

    return json;
  }

  public static JSONObject toJsonObject(Object data, Result result) {
    JSONObject json = new JSONObject();

    if (Result.kSuccess == result && null != data) {
      json.put(Consts.JSON_RES_RESULT, Result.kSuccess.ordinal());
      json.put(Consts.JSON_RES_DATA, data);
    } else {
      json.put(Consts.JSON_RES_RESULT, result.ordinal());
      json.put(Consts.JSON_RES_DATA, Consts.EMPTY_OBJECT);
    }

//    Log.i(Strings.formatJsonDate(json));
    return json;
  }

  public static JSONObject toJsonArray(Object data, Result result) {
    JSONObject json = new JSONObject();

    if (Result.kSuccess == result && null != data) {
      json.put(Consts.JSON_RES_RESULT, Result.kSuccess.ordinal());
      json.put(Consts.JSON_RES_DATA, data);
    } else {
      json.put(Consts.JSON_RES_RESULT, result.ordinal());
      json.put(Consts.JSON_RES_DATA, Consts.EMPTY_ARRAY);
    }

    return json;
  }

  @Deprecated
  public static JSONObject toJson(Page page, boolean result) {
    return toJson(page,
        result ? Result.kSuccess : Result.kException);
  }

  private static JSONArray list2array(List list) {
    JSONArray array = new JSONArray();

    if (null != list) {
      for (Object o : list) {
        array.add(compress(o));

//        if (o instanceof Model) {
//          array.add(JSONObject.parseObject(((Model) o).toJson()));
//        } else {
//          array.add(((Record) o).getColumns());
//        }
      }
    }

    return array;
  }

  public static JSONObject toJson(Page page, Result result) {
    JSONObject json = new JSONObject();

    if (Result.kSuccess == result && null != page) {
      json.put(Consts.JSON_RES_PAGE, page.getPageNumber());
      json.put(Consts.JSON_RES_SIZE, page.getPageSize());
      json.put(Consts.JSON_RES_TOTAL, page.getTotalPage());
      json.put(Consts.JSON_RES_ROWS, page.getTotalRow());
      json.put(Consts.JSON_RES_DATA, list2array(page.getList()));
    } else {
      json.put(Consts.JSON_RES_DATA, result);
    }

    json.put(Consts.JSON_RES_RESULT, result.ordinal());
    return json;
  }

  /**
   * 将 List 存储到 JSON 对象的 'data' 键值对中
   *
   * @param compress 是否压缩
   */
  public static JSONObject toJson(List list, boolean compress, Result result) {
    JSONObject json = new JSONObject();

    if (Result.kSuccess != result) {
      json.put(Consts.JSON_RES_RESULT, result.ordinal());
      json.put(Consts.JSON_RES_DATA, result);
      return json;
    }

    if (compress) {
      json.put(Consts.JSON_RES_RESULT, result.ordinal());
      json.put(Consts.JSON_RES_DATA, compressList(list));
    } else {
      JSONArray array = null;

      if (null != list && list.size() > 0) {
        Object o = list.get(0);
        if (o instanceof Model || o instanceof Record) {
          array = list2array(list);
        }
      }

      if (null != array) {
        json.put(Consts.JSON_RES_DATA, array);
      } else {
        json.put(Consts.JSON_RES_DATA, list);
      }

      json.put(Consts.JSON_RES_RESULT, Result.kSuccess.ordinal());
    }

    return json;
  }

  @Deprecated
  public static JSONObject toJson(int error) {
    JSONObject result = new JSONObject();
    result.put(Consts.JSON_RES_RESULT, -error);
    result.put(Consts.JSON_RES_DATA, Consts.EMPTY_OBJECT);
    return result;
  }

  @Deprecated
  public static JSONObject toJsonObject(Object data) {
    JSONObject json = new JSONObject();
    if (null != data) {
      json.put(Consts.JSON_RES_RESULT, Result.kSuccess.ordinal());
      json.put(Consts.JSON_RES_DATA, data);
    } else {
      json.put(Consts.JSON_RES_RESULT, Result.kNull.ordinal());
      json.put(Consts.JSON_RES_DATA, Consts.EMPTY_OBJECT);
    }
    return json;
  }

  /**
   * 生成整数结果的 JSON
   */
  @Deprecated
  public static JSONObject toJson(boolean result) {
    return result ? toJson(Result.kSuccess)
                  : toJson(Result.kNull);
  }

  public static JSONObject toJson(Result result, ModelResultType type) {
    JSONObject json = new JSONObject();
    json.put(Consts.JSON_RES_RESULT, result.ordinal());

    switch (type) {
      case kArray:
        json.put(Consts.JSON_RES_DATA, Consts.EMPTY_ARRAY);
        break;

      case kObject:
        json.put(Consts.JSON_RES_DATA, Consts.EMPTY_OBJECT);
        break;

      case kNormal:
      default:
        json.put(Consts.JSON_RES_DATA, result);
        break;

    }

    return json;
  }

  public static JSONObject toJson(Result result, String msg) {
    JSONObject json = new JSONObject();
    json.put(Consts.JSON_RES_RESULT, result.ordinal());

    if (StrKit.notBlank(msg)) {
      json.put(Consts.JSON_RES_DATA, msg);
    } else {
      json.put(Consts.JSON_RES_DATA,
          Result.kSuccess == result ? Consts.EMPTY_OBJECT : result);
    }

    return json;
  }

  public static List list(Class modelClz, String table, String exceptSelect) {
    Model model = getModelByClass(modelClz);

    List list = null;
    if (null != model) {
      list = model.dao().find("SELECT * FROM " + table + (
          StrKit.notBlank(exceptSelect) ? exceptSelect : ""));
    }

    return list;
  }

//  /**
//   * 获取 Model 类下所有数据
//   *
//   * @param compress 是否压缩
//   */
//  public static JSONObject getAll(Class modelClz, String tableName,
//      boolean compress) {
//    JSONObject result = null;
//
//    Model model = getModelByClass(modelClz);
//    if (null != model && false == StrKit.isBlank(tableName)) {
//      final String sql = "SELECT * FROM " + tableName;
//      result = toJson(model.dao().find(sql), compress);
//    }
//
//    return result;
//  }

  public static Page page(Class modelClz, String table, int page, int size,
      String exceptSelect) {
    Model model = getModelByClass(modelClz);

    Page pages = null;
    if (null != model) {
      pages = model
          .paginate(page, size, "SELECT * FROM " + table,
              null != exceptSelect ? exceptSelect : "");
    }

    return pages;
  }

  /**
   * 判断数据表中某字段的值是否存在
   */
  private static boolean isKeyExists(String tableName, String field,
      String value) {
    return null != Db.findFirst("SELECT ? FROM ? WHERE ? = ?",
        field, tableName, field, value);
  }

  public static Model getModelByClass(Class clz) {
    Model model = null;

    try {
      model = (Model) (clz.newInstance());
    } catch (Exception e) {
      // [Neo] Empty
    }

    return model;
  }

  /**
   * 判断数据表中某字段的值是否存在
   */
  public static boolean isKeyExists(String tableName, String key,
      ArrayList<String> values) {
    boolean result = true;

    // [Neo] TODO refactor in
    for (String value : values) {
      if (false == isKeyExists(tableName, key, value)) {
        result = false;
        break;
      }
    }

    return result;
  }

  /**
   * 判断 Modle 是否存在某些 id
   */
  public static boolean isIdExists(Class modelClz, JSONArray array) {
    boolean result = false;

    Model model = getModelByClass(modelClz);
    if (null == model || null == array || 0 == array.size()) {
      return result;
    }

    result = true;
    int size = array.size();

    for (int i = 0; i < size; ++i) {
      JSONObject object = array.getJSONObject(i);

      if (object.containsKey("id")) {
        if (null == model.findById(object.get("id"))) {
          result = false;
          break;
        }
      } else {
        break;
      }
    }

    return result;
  }

  /**
   * 判断 Modle 是否存在某些 id
   *
   * @param str JSON 字符串，例如 [1,2]
   */
  public static boolean isIdExists(Class modelClz, String str) {
    return isIdExists(modelClz, Strings.toJSONArray(str));
  }

  /**
   * 判断 Modle 是否存在某些 id
   */
  public static boolean isIdExists(Class modelClz, Integer[] ids) {
    boolean result = false;

    Model model = getModelByClass(modelClz);
    if (null == model || null == ids || 0 == ids.length) {
      return result;
    }

    result = true;

    for (Integer integer : ids) {
      if (null == model.findById(integer)) {
        result = false;
        break;
      }
    }

    return result;
  }

  /**
   * 判断 Modle 是否存在某些 id
   */
  public static boolean isIdExists(Class modelClz, ArrayList<String> ids) {
    boolean result = false;

    Model model = getModelByClass(modelClz);
    if (null == model || null == ids || 0 == ids.size()) {
      return result;
    }

    result = true;

    for (String value : ids) {
      if (null == model.findById(value)) {
        result = false;
        break;
      }
    }

    return result;
  }

  /**
   * 判断数据表是否存在些 id 并同步数值
   */
  public static boolean checkIdAndSyncValue(Class tableClz,
      ArrayList<String> ids, String key, JSONArray array) {
    boolean result = false;

    Model model = getModelByClass(tableClz);
    if (null == model || null == ids || null == array || StrKit.isBlank(key)
        || 0 == ids.size() || 0 == array.size()) {
      return result;
    }

    result = true;
    int size = array.size();

    for (String id : ids) {
      if (null != model.findById(id)) {

        for (int i = 0; i < size; ++i) {
          JSONObject object = array.getJSONObject(i);

          if (Integer.parseInt(id) == object.getInteger("id")) {
            model.set("id", object.getIntValue("id"))
                .set(key, object.get(key));
            model.update();
            break;
          }
        }

      } else {
        result = false;
        break;
      }
    }

    return result;
  }

  private static String getIdKey(JSONObject obj) {
    String idKey = "";

    if (obj.containsKey("id")) {
      idKey = "id";
    } else if (obj.containsKey("id#")) {
      idKey = "id#";
    } else if (obj.containsKey("id!")) {
      idKey = "id!";
    } else if (obj.containsKey("id$")) {
      idKey = "id$";
    }

    return idKey;
  }

}
